import threading
from threading import current_thread
from time import sleep
from concurrent.futures import ThreadPoolExecutor, as_completed, wait, ALL_COMPLETED, FIRST_COMPLETED


# 因为Python的线程虽然是真正的线程，但解释器执行代码时，有一个GIL锁：Global Interpreter Lock，任何Python线程执行前，必须先获得GIL锁，
# 然后，每执行100条字节码，解释器就自动释放GIL锁，让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁，所以，
# 多线程在Python中只能交替执行，即使100个线程跑在100核CPU上，也只能用到1个核
# Python虽然不能利用多线程实现多核任务，但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁，互不影响


# thread模块提供了基本的线程和锁定支持；而threading模块提供了更高级别、功能更全面的线程管理。应该避免使用thread模块，尽量使用threading模块
# 第一种方式：为Thread设置target
def fun1(n):
    print("fun1 daemon thread start n = %d" % n)
    while n > 0:
        n -= 1
        sleep(0.01)
    print("fun1 daemon thread end n = %d" % n)


def fun2(n):
    print("fun2 common thread start n = %d" % n)
    while n > 0:
        n -= 2
        sleep(0.01)
    print("fun2 common thread end n = %d" % n)


t1 = threading.Thread(target=fun1, args=(10, ))
t2 = threading.Thread(target=fun2, args=(20, ))
print(current_thread().getName(), 'first way start', "-" * 20)
# 把主线程设置为t1的守护线程的意思是，主线程退出的时候，t1也就同时退出了，即便t1的target没有执行完毕
t1.setDaemon(True)
t1.start()
t2.start()
print("main thread end ")
# fun1 daemon thread start n = 10
# fun2 common thread start n = 20
# main thread end
# fun2 common thread end n = 0
# 等待两个线程退出
t1.join()
# 加上这句，就会打印出        fun1 daemon thread end n = 0
t2.join()


# 第二种方式：通过继承重新定义子线程的run方法
class Thread(threading.Thread):
    def __init__(self, n):
        # Thread.__init__(self)
        super().__init__()
        self.__n = n

    def run(self):
        print(current_thread().getName(), ' start, n = %d' % self.__n)
        while self.__n > 0:
            self.__n -= 1
            sleep(0.01)

        print(current_thread().getName() + ' end, n = %d' % self.__n)


print(current_thread().getName(), 'second way start', "-" * 20)
t1 = Thread(10)
t2 = Thread(8)
# start必须先调用，再调用join
t1.start()
t2.start()
# 保证子线程执行完后再继续执行主线程
t1.join()
t2.join()
print(current_thread().getName(), 'end')
# MainThread start
# Thread-3  start, n = 10
# Thread-4  start, n = 8
# Thread-4 end, n = 0
# Thread-3 end, n = 0
# MainThread end


# 让一个线程等待另一个线程的另一种方式
def make_dinner():
    print("make dinner start")
    sleep(1)
    print("make dinner end")
    # 将event的标志设置为True，唤醒处于wait状态的线程
    event.set()


def eat_dinner():
    # 阻塞，等待唤醒
    event.wait()
    print("eat dinner")


event = threading.Event()
t3 = threading.Thread(target=make_dinner, args=())
t4 = threading.Thread(target=eat_dinner, args=())
print("main event", "-" * 20)
t3.start()
t4.start()
t3.join()
t4.join()
# make dinner start
# make dinner end
# eat dinner


class Boy(threading.Thread):
    def __init__(self, cond, name):
        super().__init__()
        self.__cond = cond
        self.__name = name

    def run(self):
        print("marry me?")
        # 唤醒另一个线程
        self.__cond.set()
        sleep(0.5)
        self.__cond.wait()
        print("I beg on my knees with rings")
        self.__cond.set()
        sleep(0.5)
        self.__cond.wait
        self.__cond.clear()
        print("what a clear choice!")


class Girl(threading.Thread):
    def __init__(self, cond, name):
        super().__init__()
        self.__cond = cond
        self.__name = name

    def run(self):
        self.__cond.wait()
        self.__cond.clear()
        print("no romantic")
        self.__cond.set()
        sleep(0.5)
        self.__cond.wait()
        print("all right")
        self.__cond.set()


cond = threading.Event()
b = Boy(cond, "lilei")
g = Girl(cond, "hanmeimei")
print("main condition", "-" * 20)
b.start()
g.start()
b.join()
g.join()


count = 999
# 访问共有数据，加互斥锁
lock = threading.Lock()


def fun_lock(n):
    global count
    lock.acquire()
    for i in range(1, 100000):
        # sleep(0.01)
        count += n
        count -= n
    lock.release()


t5 = threading.Thread(target=fun_lock, args=(10, ))
t6 = threading.Thread(target=fun_lock, args=(20, ))
print("main lock", "-" * 20)
print("main start count = %d" % count)
t5.start()
t6.start()
t5.join()
t6.join()
print("main end count = %d" % count)


num_sem = 30
ts = []


def fun_sem():
    global num_sem
    try:
        lock_sem.acquire()
        sleep(0.5)
        num_sem -= 1
    finally:
        lock_sem.release()
        print(current_thread().getName(), ", num = %d" % num_sem)


# 每次同时运行10个线程
lock_sem = threading.BoundedSemaphore(10)
# 创建30个线程
for i in range(num_sem):
    t = threading.Thread(target=fun_sem, args=())
    t.start()
    ts.append(t)

print("main semaphore ", "-" * 20)
# 停止30个线程
for t in ts:
    t.join()


def spider(page):
    sleep(0.5)
    print("current task: ", page)
    return page


# 创建一个具有5个线程的线程池
with ThreadPoolExecutor(max_workers=5) as t:
    print("main threadpool ", "-" * 20)

    task = []
    num_task = 10

    # 通过summit提交任务
    for i in range(num_task):
        tk = t.submit(spider, i)
        task.append(tk)

    # 通过map提交任务
    # t.map(spider, range(num_task))

    # 查看完成情况，只能被动的查看
    for i in range(num_task):
        print("task[%d]: %s" % (i, task[i].done()))

    # sleep(3)
    # wait(task, 3)
    # 等待所有子线程完成才返回
    wait(task, return_when=ALL_COMPLETED)

    # 查看完成情况，子线程完成后才会返回，否则一直阻塞
    for future in as_completed(task):
        # print("task[%d]: %s" % (future, future.result()))
        data = future.result()
        print("main: get page {}s success".format(data))


